<?php

class Marjory_Document implements Marjory_Searchable
{
    protected $uri        = '';
    protected $score      = 0;
    protected $fields     = array();
    protected $indexTypes = array();
    
    const INDEX_TYPE_UNSTORED  = 'UnStored';
    const INDEX_TYPE_KEYWORD   = 'Keyword';
    const INDEX_TYPE_BINARY    = 'Binary';
    const INDEX_TYPE_TEXT      = 'Text';
    const INDEX_TYPE_UNINDEXED = 'UnIndexed';
    
    public static $useHtmlExtractor = false;
    public static $abstractMaxChars = 200;
    public static $tempDir          = '/tmp';
    
    public function __construct($uri='')
    {
        if (empty($uri)) {
            throw new Marjory_Exception('Every document must have a Unique Resource Identifier, but none was provided.');
        }
        $this->uri = $uri;
    }
    
    public function setField($fieldName, $value, $indexType=false)
    {
        $this->fields[$fieldName] = $value;
        if ($indexType !== false) {
            // add validation of index type against class constants (see Lucene field types)
            $this->indexTypes[$fieldName] = $indexType;
        }
    }
    
    public function getURI()
    {
        return $this->uri;
    }
    
    public function setScore($score)
    {
        $this->score = $score;
    }
    
    public function getScore()
    {
        return $this->score;
    }
    
    public function getField($fieldName)
    {
        if (!isset($this->fields[$fieldName])) {
            throw new Marjory_Exception("Field '$fieldName' does not exist in document.");
        }
        return $this->fields[$fieldName];
    }
    
    public function getFieldNames()
    {
        return array_keys($this->fields);
    }
    
    public function getIndexType($fieldName)
    {
        if (!isset($this->indexTypes[$fieldName])) {
            return self::INDEX_TYPE_TEXT;
        }
        return $this->indexTypes[$fieldName];
    }
    
    public function setIndexType($fieldName, $type)
    {
        $validTypes = array(self::INDEX_TYPE_BINARY,
                            self::INDEX_TYPE_KEYWORD,
                            self::INDEX_TYPE_TEXT,
                            self::INDEX_TYPE_UNINDEXED,
                            self::INDEX_TYPE_UNSTORED);
        
        if (in_array($type, $validTypes)) {
            $this->indexTypes[$fieldName] = $type;
            return true;
        } else {
            throw new Marjory_Exception("'$type' is not a valid field type.");
        }
    }
    
    /**
     * @param $xml A SimpleXML object containing the relevant elements
     * @return Marjory_Document
     * @TODO Don't restrict access to SimpleXML, allow raw XML strings and DOM objects as well
     */
    public static function fromXML(SimpleXMLElement $xml)
    {
        if (isset($xml->uri)) {
            throw new Marjory_Exception('Document does not contain an "uri" element.');
        }
        $doc = new Marjory_Document((string)$xml->uri);
        $fields = $xml->xpath('//field');
        foreach($fields as $field) {
            $doc->setField((string)$field['name'], (string)$field);
        }
        return $doc;
    }
    
    /**
     * Uses the HTML parsing capabilities of Zend_Search_Lucene
     * to extract meta-information from a string containing a HTML
     * document. Information is stored in Marjory's default field set:
     *  * title    (Text)
     *  * content  (UnStored)
     *  * abstract (UnIndexed)
     *
     * Alternatively, you can use your own HTML parsing class to
     * extract the fields as desired, by setting the static property
     * $useHtmlExtractor to the name of your parser class.
     * The class must implement the public method "getFieldValue($fieldName)" 
     * and provide information on the fields "title", "content" and "abstract". 
     * The class constructor must accept the HTML document as a string.
     * 
     * If the static property $abstractMaxChars is set, the abstract will
     * be shortened to the desired length.
     * 
     * @param string $uri
     * @param string $htmlDocument
     * @return Marjory_Document 
     */
    public static function fromHTML($uri, $htmlDocument)
    {
        if (self::$useHtmlExtractor && class_exists(self::$useHtmlExtractor, true)) {
            //error_log('Using custom HtmlExctractor');
            $htmlDoc = new self::$useHtmlExtractor($htmlDocument);
        } else {
            //error_log('Using Zend_Search_Lucene HTML extraction');
            $htmlDoc = Zend_Search_Lucene_Document_Html::loadHTML($htmlDocument);
        }
        $doc = new Marjory_Document($uri);
        $doc->setField('title', $htmlDoc->getFieldValue('title'));
        $doc->setIndexType('title', self::INDEX_TYPE_TEXT);
        if (self::$useHtmlExtractor && class_exists(self::$useHtmlExtractor, true)) {
            $contentField = 'content';
        } else {
            $contentField = 'body'; // for Lucene parser
        }
        //error_log('Using content field: '.$contentField);
        $doc->setField('content', $htmlDoc->getFieldValue($contentField));
        $doc->setIndexType('content', self::INDEX_TYPE_UNSTORED);
        $abstract = $htmlDoc->getFieldValue($contentField);
        if (self::$abstractMaxChars) {
            $abstract = substr($abstract, 0, self::$abstractMaxChars);
        }
        $doc->setField('abstract', $abstract);
        $doc->setIndexType('abstract', self::INDEX_TYPE_UNINDEXED);
        return $doc;
    }

    /**
     * Creates a Marjory_Document from a Word 2007 file, using the parser
     * provided by Zend_Search_Lucene.
     *
     * @param string $uri
     * @param string $filePath
     * @return Marjory_Document
     */
    public static function fromDocx($uri, $filePath)
    {
        return self::fromLuceneDocument($uri, Zend_Search_Lucene_Document_Docx::loadDocxFile($filename));
    }

    /**
     * Creates a Marjory_Document from an Excel 2007 file, using the parser
     * provided by Zend_Search_Lucene.
     *
     * @param string $uri
     * @param string $filePath
     * @return Marjory_Document
     */
    public static function fromXlsx($uri, $filePath)
    {
        return self::fromLuceneDocument($uri, Zend_Search_Lucene_Document_Xlsx::loadXlsxFile($filename));
    }

    /**
     * Creates a Marjory_Document from a PowerPoint 2007 file, using the parser
     * provided by Zend_Search_Lucene.
     *
     * @param string $uri
     * @param string $filePath
     * @return Marjory_Document
     */
    public static function fromPptx($uri, $filePath)
    {
        return self::fromLuceneDocument($uri, Zend_Search_Lucene_Document_Pptx::loadPptxFile($filename));
    }


    public static function fromPdf($uri, $filePath)
    {
        $tempFileName = uniqid('MarjoryPDF', true);
        exec("pdftotext $filePath ".self::TEMP_DIR.DIRECTORY_SEPARATOR.$tempFileName);
        $text = file_get_contents($tempFileName);
        unlink($tempFileName);

        $doc = new Marjory_Document($uri);
        $doc->setField('title', basename($filePath));
        $doc->setIndexType('title', self::INDEX_TYPE_TEXT);
        $doc->setField('content', $text);
        $doc->setIndexType('content', self::INDEX_TYPE_UNSTORED);
        $doc->setField('abstract', self::createAbstract($text));
        $doc->setIndexType('abstract', self::INDEX_TYPE_UNINDEXED);
        return $doc;
    }

    /**
     * Takes a Zend_Search_Lucene_Document object and converts it to a
     * Marjory_Document. Lucene document is required to at least contain the fields
     * "title" and "body", which should be true for all Lucene documents that
     * are automatically created from files by Zend_Search_Lucene.
     *
     * @param string $uri
     * @param Zend_Search_Lucene_Document $luceneDoc
     * @return Marjory_Document
     */
    protected static function fromLuceneDocument($uri, Zend_Search_Lucene_Document $luceneDoc)
    {
        $doc = new Marjory_Document($uri);
        $doc->setField('title', $luceneDoc->getFieldValue('title'));
        $doc->setIndexType('title', self::INDEX_TYPE_TEXT);
        $doc->setField('content', $luceneDoc->getFieldValue('body'));
        $doc->setIndexType('content', self::INDEX_TYPE_UNSTORED);
        $doc->setField('abstract', self::createAbstract($luceneDoc->getFieldValue('body')));
        $doc->setIndexType('abstract', self::INDEX_TYPE_UNINDEXED);
        return $doc;
    }

    /**
     * Returns the first few characters from a string, using self::$abstractMaxChars
     * as a maximum length.
     * If mbstring extension is installed, mb_substr() will be used instead of
     * plain substr().
     *
     * @param string $text
     * @return string
     */
    protected static function createAbstract($text)
    {
        if (function_exists('mb_substr')) {
            return mb_substr($test, 0, self::$abstractMaxChars);
        }
        return substr($text, 0, self::$abstractMaxChars);
    }
    
    /* @TODO Add methods fromJSON, fromArray, fromWhatever... and maybe a factory method with input type auto-detection :-) */
}